package rov.data;


import java.io.File;
import java.io.PrintWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Calendar;


// provides a simple way to log data to disk
public class LogManager
{
    public static final File DEFAULT_LOG_DIR = new File(
                        System.getenv("HOME") + File.separatorChar + ".rovlog");
    public static final String IMU_LOG = "imu.txt";
    public static final String CMD_LOG = "cmd.txt";
    public static final String SENSOR_LOG = "sensor.txt";


    private static final Object sInitLock = new Object();
    private static volatile LogManager sShared;


    // returns an object shared by the entire application
    public static final LogManager getShared()
    {
        if (sShared == null)
        {
            synchronized (sInitLock)
            {
                if (sShared == null)
                {
                    sShared = new LogManager(DEFAULT_LOG_DIR);
                }
            }
        }

        return sShared;
    }


    private Object mLogsLock;
    private Hashtable<String,Log> mLogs;
    private File mLogDir;
    private File mRunDir;


    // getShared() is the recommended way to create a log manager
    public LogManager(File logDir)
    {
        mLogsLock = new Object();
        mLogs = new Hashtable<String,Log>();

        mLogDir = logDir;
        mLogDir.mkdir();

        Calendar cal = Calendar.getInstance();
        String dayDirName = cal.get(Calendar.YEAR) + "-"
                          + (cal.get(Calendar.MONTH) + 1) + "-"
                          + cal.get(Calendar.DAY_OF_MONTH);

        File dayDir = new File(mLogDir, dayDirName);
        dayDir.mkdir();

        int runNum = 1;
        File runDir = new File(dayDir, runNum + "");

        while (runDir.exists())
        {
            runDir = new File(dayDir, runNum + "");
            runNum++;
        }

        runDir.mkdir();
        mRunDir = runDir;
    }


    // returns null if logging has been disabled via closeAll()
    private Log createOrGetLog(String logName)
    {
        if (mLogs == null)
            return null;

        Log log = mLogs.get(logName);

        if (log == null)
        {
            synchronized (mLogsLock)
            {
                if (mLogs.get(logName) == null)
                    mLogs.put(logName, new Log(logName, mRunDir));
            }

            log = mLogs.get(logName);
        }

        return log;
    }


    // append msg and timestamp to a log file specified by logName
    public void log(String logName, String msg)
    {
        Log log = createOrGetLog(logName);

        if (log != null)
            log.append(msg);
    }


    // close all log files and disable logging
    public void closeAll()
    {
        synchronized (mLogsLock)
        {
            for (String key : mLogs.keySet())
            {
                mLogs.get(key).close();
            }

            mLogs.clear();
            mLogs = null;
        }
    }


    // manages a single log file
    private static class Log
    {
        private String mName;
        private File mLog;
        private PrintWriter mOut;


        // creates/appends to a log file called name in the directory runDir
        public Log(String name, File runDir)
        {
            mName = name;
            mLog = new File(runDir, name);

            try
            {
                mOut = new PrintWriter(new FileOutputStream(mLog, true));
                mLog.setReadOnly();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }


        public void close()
        {
            try
            {
                mOut.flush();
                mOut.close();
            }
            finally
            {
                mOut = null;
            }
        }


        // appends a message with timestamp to the log file
        public synchronized void append(String msg)
        {
            if (mOut != null)
            {
                Calendar cal = Calendar.getInstance();
                String prefix = cal.getTimeInMillis() + "\t"
                              + cal.get(Calendar.YEAR) + "-"
                              + (cal.get(Calendar.MONTH) + 1) + "-"
                              + cal.get(Calendar.DAY_OF_MONTH) + "-"
                              + cal.get(Calendar.HOUR_OF_DAY) + ":"
                              + cal.get(Calendar.MINUTE) + ":"
                              + cal.get(Calendar.SECOND) + ":"
                              + cal.get(Calendar.MILLISECOND) + "\t";

                mOut.println(prefix + msg);
            }
        }
    }


    // unit test
    public static void main(String[] args)
    {
        LogManager lm = getShared();

        lm.log(IMU_LOG, "ohey1");
        lm.log(IMU_LOG, "ohey2");

        lm.closeAll();
    }
}
